package linc.spring.framework;

import linc.spring.framework.annotation.*;
import linc.spring.framework.enums.ScopeEnums;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author yqlin
 * @date 2021/9/3 23:11
 * @description
 */
public class MyApplicationContext {
    /**
     * 配置类
     */
    private final Class<?> configurationClass;
    /**
     * Map<beanName, BeanDefinition>
     * bean定义对象的映射，按bean名称键控。
     */
    private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(256);

    /**
     * 单例池
     * 可共享单例对象的缓存:从bean名称到bean实例。
     */
    private final Map<String, Object> singletonObjects = new HashMap<>(256);

    private final List<File> classFileList = new ArrayList<>();
    private final List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public MyApplicationContext(Class<?> configurationClass) {
        this.configurationClass = configurationClass;
        // 扫描Component-->beanDefinitionMap
        scanComponents(configurationClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            // 如果是单例我们就创建bean，
            if (ScopeEnums.singleton.equals(beanDefinition.getScope())) {
                Object instance = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, instance);
            }
        }
        // System.out.println(beanDefinitionMap);
    }

    /**
     * 创建Bean实例
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class<?> clazz = beanDefinition.getClazz();
        try {
            // 反射创建实例
            Object instance = clazz.getConstructor().newInstance();
            // 如果对象依赖注入了的，就给它进行赋值
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(instance, getBean(field.getName()));
                }
            }
            // Aware回调接口
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            // 初始化前
            // BeanPostProcessor -> postProcessBeforeInitialization
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
            }
            // 初始化bean前 @PostConstruct
            for (Method method : clazz.getDeclaredMethods()) {
                if (method.isAnnotationPresent(PostConstruct.class)) {
                    method.setAccessible(true);
                    method.invoke(instance);
                }
            }
            // 初始化 InitializingBean
            if (instance instanceof InitializingBean) {
                ((InitializingBean) instance).afterPropertiesSet();
            }
            // 初始化后
            // BeanPostProcessor -> postProcessAfterInitialization
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
            }
            return instance;
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 扫描Component
     */
    private void scanComponents(Class<?> configurationClass) {
        if (configurationClass.isAnnotationPresent(ComponentScan.class)) {
            String[] basePackages = configurationClass.getAnnotation(ComponentScan.class).basePackages();
            for (String basePackage : basePackages) {
                // linc.spring.demo -> linc/spring/demo
                String path = basePackage.replace(".", "/");
                ClassLoader classLoader = this.getClass().getClassLoader();
                URL resource = classLoader.getResource(path);
                assert resource != null;
                File file = new File(resource.getFile());
                // 判断是否是目录 找到所有类文件
                listClassFiles(file);
                composeBeanDefinition(classLoader, classFileList.toArray(new File[0]));
            }
        }
    }

    /**
     * 列举出目录下所有文件
     */
    private void listClassFiles(File file) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            if (files.length == 0) {
                return;
            }
            for (File f : files) {
                classFileList.add(f);
                if (f.isDirectory()) {
                    listClassFiles(f);
                }
            }
        }
    }

    private void composeBeanDefinition(ClassLoader classLoader, File[] files) {
        for (File f : files) {
            String absolutePath = f.getAbsolutePath();
            if (absolutePath.contains(".class")) {
                // linc/spring/demo/Main -> linc.spring.demo.Main
                String osName = System.getProperty("os.name");
                if (osName.startsWith("Windows")) {
                    // windows
                    absolutePath = absolutePath.substring(absolutePath.indexOf("target\\classes") + 15, absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("\\", ".");
                } else {
                    // unix or linux
                    absolutePath = absolutePath.substring(absolutePath.indexOf("target/classes") + 15, absolutePath.indexOf(".class"));
                    absolutePath = absolutePath.replace("/", ".");
                }
                // 加载target目录下类
                try {
                    Class<?> clazz = classLoader.loadClass(absolutePath);
                    // 判断该类是否有@Component 有就创建实例
                    if (clazz.isAnnotationPresent(Component.class)) {
                        // 被继承
                        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                            BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                            beanPostProcessorList.add(instance);
                        }
                        Component componentAnnotation = clazz.getAnnotation(Component.class);
                        String beanName = componentAnnotation.name();
                        // 如果beanName不填写，就默认用类名生成小写
                        if ("".equals(beanName)) {
                            beanName = Introspector.decapitalize(clazz.getSimpleName());
                        }
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);
                        // 判断bean是否是单例的,如果有值就按照传递的否则按照单例默认值进行
                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                            ScopeEnums scope = scopeAnnotation.value();
                            beanDefinition.setScope(scope);
                        } else {
                            beanDefinition.setScope(ScopeEnums.singleton);
                        }
                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 获取bean
     */
    public Object getBean(String beanName) {
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException(String.format("%s不存在", beanName));
        }
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 如果是单例就返回单例池中的对象,否则返回新的实例
        if (ScopeEnums.singleton.equals(beanDefinition.getScope())) {
            Object singletonBean = singletonObjects.get(beanName);
            if (Objects.isNull(singletonBean)) {
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, singletonBean);
            }
            return singletonBean;
        }
        // 原型 prototype
        return createBean(beanName, beanDefinition);
    }
}
